import type { DatabaseStoreConfig } from '../type'

/**
 * IndexedDB 多存储对象管理类（泛型支持）
 *
 * @description 单数据库单连接，支持批量初始化存储对象，动态操作任意已初始化的 store
 * @example
 * // 初始化数据库并创建两个存储对象
 * const db = await IDBManager.getInstance('myDB', 1, [
 *   { name: 'users', keyPath: 'id' },
 *   { name: 'products', keyPath: 'sku', autoIncrement: true }
 * ]);
 * // 操作不同存储对象
 * await db.set('users', { id: 1, name: '张三' });
 * await db.get('products', 'p123');
 */
class IDBManager {
  // 单例缓存：键格式为 "数据库名-版本号"
  private static instanceCache = new Map<string, IDBManager>()
  // 数据库连接
  private db: IDBDatabase | null = null
  // 数据库名称
  private readonly dbName: string
  // 数据库版本
  private readonly version: number
  // 已初始化的存储对象配置
  private readonly storeConfigs: DatabaseStoreConfig[]

  /**
   * 私有构造函数（强制通过 getInstance 获取实例）
   */
  private constructor(dbName: string, version: number, storeConfigs: DatabaseStoreConfig[]) {
    this.dbName = dbName
    this.version = version
    this.storeConfigs = storeConfigs
  }

  /**
   * 获取单例实例（批量初始化存储对象）
   * @param dbName 数据库名称
   * @param version 数据库版本（需递增以添加新存储对象）
   * @param storeConfigs 需要初始化的存储对象配置数组
   * @returns 单例实例 Promise
   */
  static async getInstance(
    dbName: string,
    version: number = 1,
    storeConfigs: DatabaseStoreConfig[] = [],
  ): Promise<IDBManager> {
    const cacheKey = `${dbName}-${version}`
    let instance = IDBManager.instanceCache.get(cacheKey)

    if (!instance) {
      instance = new IDBManager(dbName, version, storeConfigs)
      await instance.initializeDB()
      IDBManager.instanceCache.set(cacheKey, instance)
    }

    return instance
  }

  /**
   * 初始化数据库连接（批量检查/创建存储对象）
   */
  private async initializeDB(): Promise<void> {
    return new Promise((resolve, reject) => {
      if (this.db) return resolve() // 已有有效连接直接返回

      const request = indexedDB.open(this.dbName, this.version)

      // 数据库升级/初始化事件（关键逻辑）
      request.onupgradeneeded = () => {
        const db = request.result
        // 遍历所有配置的存储对象，按需创建
        this.storeConfigs.forEach((config) => {
          if (!db.objectStoreNames.contains(config.name)) {
            db.createObjectStore(config.name, {
              keyPath: 'id',
              autoIncrement: config.autoIncrement ?? false,
            })
          }
        })
      }

      // 连接成功回调
      request.onsuccess = () => {
        this.db = request.result
        this.db.onclose = () => (this.db = null) // 清理连接缓存
        resolve()
      }

      // 连接失败回调
      request.onerror = () => {
        reject(new Error(`数据库连接失败: ${request.error?.message}`))
      }
    })
  }

  /**
   * 根据主键获取数据
   * @param storeName 目标存储对象名称
   * @param key 主键值
   * @returns 数据对象或 undefined（未找到）
   */
  async get<T>(storeName: string, key: IDBValidKey): Promise<T | undefined> {
    await this.checkStoreExists(storeName)
    return this.transactionOperation(storeName, 'readonly', (store) => store.get(key))
  }

  /**
   * 添加或更新数据到指定存储对象
   * @param storeName 目标存储对象名称
   * @param data 要处理的数据（需包含主键字段）
   */
  async set<T>(storeName: string, data: T | T[]): Promise<void> {
    await this.checkStoreExists(storeName)
    if (Array.isArray(data)) {
      await Promise.all(
        data.map((item) =>
          this.transactionOperation(storeName, 'readwrite', (store) => store.put(item)),
        ),
      )
    } else {
      await this.transactionOperation(storeName, 'readwrite', (store) => store.put(data))
    }
  }

  /**
   * 根据主键或主键数组删除数据
   * @param storeName 目标存储对象名称
   * @param key 主键值或主键值数组
   */
  async delete(storeName: string, key: IDBValidKey | IDBValidKey[]): Promise<void> {
    await this.checkStoreExists(storeName)
    if (Array.isArray(key)) {
      await Promise.all(
        key.map((k) =>
          this.transactionOperation(storeName, 'readwrite', (store) => store.delete(k)),
        ),
      )
    } else {
      await this.transactionOperation(storeName, 'readwrite', (store) => store.delete(key))
    }
  }

  /**
   * 清空指定存储对象的所有数据
   * @param storeName 目标存储对象名称
   */
  async clear(storeName: string): Promise<void> {
    await this.checkStoreExists(storeName)
    await this.transactionOperation(storeName, 'readwrite', (store) => store.clear())
  }

  /**
   * 获取指定存储对象的所有数据
   * @param storeName 目标存储对象名称
   * @returns 数据数组
   */
  async getAll<T>(storeName: string): Promise<T[]> {
    await this.checkStoreExists(storeName)
    return this.transactionOperation(storeName, 'readonly', (store) => store.getAll())
  }

  /**
   * 通用事务操作方法（私有）
   */
  private async transactionOperation<R>(
    storeName: string,
    mode: IDBTransactionMode,
    operation: (store: IDBObjectStore) => IDBRequest<R>,
  ): Promise<R> {
    return new Promise((resolve, reject) => {
      const transaction = this.db!.transaction(storeName, mode)
      const store = transaction.objectStore(storeName)
      const request = operation(store)

      request.onsuccess = () => resolve(request.result)
      request.onerror = () => reject(new Error(`操作失败: ${request.error?.message}`))
      transaction.onerror = () => reject(new Error(`事务失败: ${transaction.error?.message}`))
    })
  }

  /**
   * 检查存储对象是否存在（私有）
   */
  private async checkStoreExists(storeName: string): Promise<void> {
    if (!this.db) await this.initializeDB()
    if (!this.db?.objectStoreNames.contains(storeName)) {
      throw new Error(`存储对象 ${storeName} 未初始化（请检查版本号或配置）`)
    }
  }
}

export default IDBManager
